package com.flyqiu.flow.core.tools;

import com.flyqiu.common.tools.FlyQiuCollectionTools;
import com.flyqiu.flow.bpmn.FlyQiuBpmModeler;
import com.flyqiu.flow.bpmn.element.ProcNodeModel;
import com.flyqiu.flow.bpmn.element.SequenceFlow;
import com.flyqiu.flow.core.fliter.BpmnElementFilter;

import com.flyqiu.flow.bpmn.element.BaseProcElement;


import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class BpmnElementFind {

    private final BaseProcElement startElement;

    private BpmnElementFilter filter = null;

    private final DispatchBpmnElementFilter dispatch = new DispatchBpmnElementFilter();

    public BpmnElementFind(FlyQiuBpmModeler bpmModeler) {
        startElement = bpmModeler.getMainProcess().getInitialFlowElement();
    }

    public BpmnElementFind(FlyQiuBpmModeler bpmModeler, String startDefKey) {
        startElement = bpmModeler.getFlowElement(startDefKey);
    }

    public BpmnElementFind(BaseProcElement startElement) {
        this.startElement = startElement;
    }

    public void setFilter(BpmnElementFilter filter) {
        this.filter = filter;
    }

    public List<BaseProcElement> find() {
        List<BaseProcElement> result = new ArrayList<>();
        if (filter == null) {
            return result;
        }
        HashSet<String> set = new HashSet<>();
        BaseProcElement element = startElement;
        if (dispatch.isIncludeStartingElement()) {
            find(element, 0, result, set);
            return result;
        }
        if (element instanceof ProcNodeModel) {
            List<SequenceFlow> outgoingFlows = ((ProcNodeModel) element).getOutgoingFlows();
            FlyQiuCollectionTools.iterator(outgoingFlows, (outgoingFlow) -> {
                find(outgoingFlow, 0, result, set);
            });
        } else if (element instanceof SequenceFlow) {
            BaseProcElement targetNode = ((SequenceFlow) element).getTargetNode();
            find(targetNode, 0, result, set);
        }


        return result;
    }

    private void find(BaseProcElement element, int level, List<BaseProcElement> result, Set<String> set) {
        if (set.contains(element.getDefKey())) {
            return;
        }
        set.add(element.getDefKey());

        int tempLevel = level;
        if (dispatch.selectedElementType(element.getElementType())) {
            tempLevel += 1;
            if (dispatch.filter(level, element)) {
                result.add(element);
            }
        }

        if (!dispatch.continueNextLevel(level, tempLevel)) {
            return;
        }
        if (element instanceof SequenceFlow) {
            BaseProcElement targetNode = ((SequenceFlow) element).getTargetNode();
            find(targetNode, tempLevel, result, set);
        } else if (element instanceof ProcNodeModel) {
            List<SequenceFlow> outgoingFlows = ((ProcNodeModel) element).getOutgoingFlows();
            for (int i = 0; i < outgoingFlows.size(); i++) {
                find(outgoingFlows.get(0), tempLevel, result, set);
            }
        }
    }

    private class DispatchBpmnElementFilter implements BpmnElementFilter {
        @Override
        public boolean isIncludeStartingElement() {
            if (filter == null) return false;
            return filter.isIncludeStartingElement();
        }

        @Override
        public boolean filter(int level, BaseProcElement flowElement) {
            if (filter == null) return false;
            return filter.filter(level, flowElement);

        }

        @Override
        public boolean continueNextLevel(int currentLevel, int nextLevel) {
            if (filter == null) return BpmnElementFilter.super.continueNextLevel(currentLevel, nextLevel);
            return filter.continueNextLevel(currentLevel, nextLevel);
        }

        @Override
        public boolean handlerSequenceFlow(SequenceFlow sequenceFlow) {
            if (filter == null) return BpmnElementFilter.super.handlerSequenceFlow(sequenceFlow);
            return filter.handlerSequenceFlow(sequenceFlow);
        }

        /**
         * @param elementType
         * @return
         */
        @Override
        public boolean selectedElementType(String elementType) {
            if (filter == null) return BpmnElementFilter.super.selectedElementType(elementType);
            return filter.selectedElementType(elementType);
        }
    }

}
